#include "stdafx.h"

#include "OpenGLCommon.h"
#include "OpenGLDevice.h"
#include "Texture.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"

#ifdef MAC
#include "AppKit/AppKit.h"
#endif

using namespace Hiranipra;
using namespace Hiranipra::Platform;
using namespace Hiranipra::Platform::Graphics;

OpenGLDevice::OpenGLDevice( const Adapter& adapter, const Resolution& resolution, const DeviceParameters& parameters ) :
	_adapter( adapter ), _resolution( resolution ), _parameters( parameters ),
	_frameCount( 0 ), _inFrame( false ),
	_arrayState( 0 )
#ifdef WIN32
	, _hWnd( NULL ), _hDC( NULL ), _hRC( NULL )
#elif defined( MACOSX )
#endif
{
	_window = parameters.Window;
	_window->Retain();
	_window->EventSource.Add( this );

	int colorBits = resolution.ColorBits;
	int depthBits = resolution.DepthBits;
	int stencilBits = resolution.StencilBits;

#ifdef WIN32
	
	_hWnd = ( HWND )_window->GetHandle();
	_hDC = GetDC( _hWnd );

	static PIXELFORMATDESCRIPTOR pfd =
	{
		sizeof( PIXELFORMATDESCRIPTOR ),
		1,
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA,
		colorBits,									// Color buffer bits
		0, 0, 0, 0, 0, 0,
		0,											// Alpha buffer bits
		0,
		0,											// Accumulation buffer bits
		0, 0, 0, 0,
		depthBits,									// Depth buffer bits
		stencilBits,								// Stencil buffer bits
		0,
		PFD_MAIN_PLANE,
		0,
		0, 0, 0
	};
	
	int pixelFormat = ChoosePixelFormat( _hDC, &pfd );
	SetPixelFormat( _hDC, pixelFormat, &pfd );

	_hRC = wglCreateContext( _hDC );
	wglMakeCurrent( _hDC, _hRC );

#elif defined( MACOSX )

	NSWindow* window = ( NSWindow* )_window->GetHandle();
	
	NSOpenGLPixelFormatAttribute pfa[] = {
		NSOpenGLPFADoubleBuffer,
		NSOpenGLPFAAccelerated,
		NSOpenGLPFAColorSize,
		(NSOpenGLPixelFormatAttribute)colorBits,
		NSOpenGLPFADepthSize,
		(NSOpenGLPixelFormatAttribute)depthBits,
		SOpenGLPixelFormatAttribute)0
	};
	NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:pfa];

#endif

	this->OnResize();

	glShadeModel( GL_SMOOTH );
	glClearColor( 0.0f, 0.0f, 0.0f, 0.5f );
	glClearDepth( 1.0f );
	glEnable( GL_DEPTH_TEST );
	glDepthFunc( GL_LEQUAL );
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	glEnable( GL_TEXTURE_2D );

	_textureLoader = new TextureLoader( this );
	// TODO: create
	_programLoader = NULL;
	_meshLoader = NULL;

	glewInit();
}

OpenGLDevice::~OpenGLDevice()
{
	RELEASE( _textureLoader );
	RELEASE( _programLoader );
	RELEASE( _meshLoader );

#ifdef WIN32
	wglMakeCurrent( NULL, NULL );
	wglDeleteContext( _hRC );
	_hRC = NULL;
	if( _hDC != NULL )
	{
		ReleaseDC( _hWnd, _hDC );
		_hDC = NULL;
	}
	_hWnd = NULL;
#elif defined( MACOSX )
#endif

	_window->EventSource.Remove( this );
	RELEASE( _window );
}

ITexture* OpenGLDevice::CreateTexture( const BufferFlags flags, const uint32 width, const uint32 height, const PixelFormat format, const void* buffer ) const
{
	return NULL;
}

IVertexBuffer* OpenGLDevice::CreateVertexBuffer( const BufferFlags flags, VertexDeclaration* vertexDecl, const uint32 elementCount, const void* buffer ) const
{
	return new VertexBuffer( flags, vertexDecl, elementCount, buffer );
}

IIndexBuffer* OpenGLDevice::CreateIndexBuffer( const BufferFlags flags, const uint32 elementCount, const uint32 elementSize, const void* buffer ) const
{
	return new IndexBuffer( flags, elementCount, elementSize, buffer );
}

void OpenGLDevice::BeginFrame( int clearFlags )
{
	DASSERT( _inFrame == false, "Unmatched Begin/EndFrame()" );
	if( _inFrame == true )
		return;
	_inFrame = true;

	GLbitfield flags = 0;
	if( ( clearFlags & ClearColor ) == ClearColor )
		flags |= GL_COLOR_BUFFER_BIT;
	if( ( clearFlags & ClearDepth ) == ClearDepth )
		flags |= GL_DEPTH_BUFFER_BIT;
	if( ( clearFlags & ClearStencil ) == ClearStencil )
		flags |= GL_STENCIL_BUFFER_BIT;
	if( flags != 0 )
		glClear( flags );

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
}

void OpenGLDevice::EndFrame()
{
	DASSERT( _inFrame == true, "Unmatched Begin/EndFrame()" );
	if( _inFrame == false )
		return;

	glFlush();
#ifdef WIN32
	SwapBuffers( _hDC );
#elif defined( MACOSX )
	// flushBuffer
#warning Swap buffers
#endif

	_inFrame = false;
	_frameCount++;
}

void OpenGLDevice::SetTexture( uint32 stage, ITexture* texture )
{
	Texture* glTexture = dynamic_cast<Texture*>( texture );
	glTexture->Activate( stage );
}

#define ARRAY_MASK_VERTEX		1 << 1
#define ARRAY_MASK_NORMAL		1 << 2
#define ARRAY_MASK_TEXCOORD		1 << 3
#define ARRAY_MASK_COLOR		1 << 4
#define ARRAY_STATE_SET( currentState, newState, mask, glId ) if( ( currentState & mask ) != ( newState & mask ) ) { if( ( newState & mask ) != 0 ) glEnableClientState( glId ); else glDisableClientState( glId ); }

uint32 GetVertexDataType( VertexElementFormat format )
{
	switch( format )
	{
	default:
		return 0;
	case VertexFormatSingle:
	case VertexFormatVector2:
	case VertexFormatVector3:
	case VertexFormatVector4:
		return GL_FLOAT;
	case VertexFormatRgba32:
		return GL_UNSIGNED_INT;
	case VertexFormatByte4:
		return GL_UNSIGNED_INT;
	case VertexFormatShort2:
	case VertexFormatShort4:
		return GL_SHORT;
	};
}

void OpenGLDevice::RenderCorePre( IVertexBuffer* vertexBuffer )
{
	VertexBuffer* vb = ( VertexBuffer* )vertexBuffer;
	VertexDeclaration* decl = vb->GetDeclaration();
	if( decl->InternalTag == 0 )
	{
		for( uint32 n = 0; n < decl->Elements.size(); n++ )
		{
			VertexElement& el = decl->Elements[ n ];
			el.InternalTag2 = GetVertexDataType( el.Format );
			switch( el.Usage )
			{
			case VertexUsagePosition:			decl->InternalTag |= ARRAY_MASK_VERTEX;		break;
			case VertexUsageNormal:				decl->InternalTag |= ARRAY_MASK_NORMAL;		break;
			case VertexUsageTextureCoordinate:	decl->InternalTag |= ARRAY_MASK_TEXCOORD;	break;
			case VertexUsageColor:				decl->InternalTag |= ARRAY_MASK_COLOR;		break;
			case VertexUsageBlendWeight:
			case VertexUsageBlendIndices:
			case VertexUsagePointSize:
			case VertexUsageTangent:
			case VertexUsageBinormal:
			case VertexUsageDepth:
			case VertexUsageSample:
				DASSERT( false, "Unimplemented vertex usage type" );
				break;
			}
		}
	}

	glBindBuffer( GL_ARRAY_BUFFER, vb->GetBufferID() );

	uint32 newArrayState = decl->InternalTag;
	if( _arrayState != newArrayState )
	{
		ARRAY_STATE_SET( _arrayState, newArrayState, ARRAY_MASK_VERTEX,		GL_VERTEX_ARRAY );
		ARRAY_STATE_SET( _arrayState, newArrayState, ARRAY_MASK_NORMAL,		GL_NORMAL_ARRAY );
		ARRAY_STATE_SET( _arrayState, newArrayState, ARRAY_MASK_TEXCOORD,	GL_TEXTURE_COORD_ARRAY );
		ARRAY_STATE_SET( _arrayState, newArrayState, ARRAY_MASK_COLOR,		GL_COLOR_ARRAY );
		_arrayState = newArrayState;
	}

	uint32 stride = decl->GetStrideSize();
	for( uint32 n = 0; n < decl->Elements.size(); n++ )
	{
		VertexElement& el = decl->Elements[ n ];
		switch( el.Usage )
		{
		case VertexUsagePosition:			glVertexPointer( el.Count, el.InternalTag2, stride, ( void* )el.Offset );			break;
		case VertexUsageNormal:				glNormalPointer( el.InternalTag2, stride, ( void* )el.Offset );						break;
		case VertexUsageTextureCoordinate:	glTexCoordPointer( el.Count, el.InternalTag2, stride, ( void* )el.Offset );			break;
		case VertexUsageColor:				glColorPointer( el.Count, el.InternalTag2, stride, ( void* )el.Offset );			break;
		case VertexUsageBlendWeight:
		case VertexUsageBlendIndices:
		case VertexUsagePointSize:
		case VertexUsageTangent:
		case VertexUsageBinormal:
		case VertexUsageDepth:
		case VertexUsageSample:
			DASSERT( false, "Unimplemented vertex usage type" );
			break;
		}
	}
}

void OpenGLDevice::RenderBuffer( const PrimitiveType primitiveType, IVertexBuffer* vertexBuffer )
{
	this->RenderCorePre( vertexBuffer );

	glDrawArrays( ( GLenum )primitiveType, 0, vertexBuffer->GetElementCount() );
}

void OpenGLDevice::RenderIndexedBuffer( const PrimitiveType primitiveType, IVertexBuffer* vertexBuffer, IIndexBuffer* indexBuffer )
{
	this->RenderCorePre( vertexBuffer );

	IndexBuffer* ib = ( IndexBuffer* )indexBuffer;
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ib->GetBufferID() );

	uint32 width;
	DASSERT( ib->GetElementSize() < 4, "32 bit index buffers not supported" );
	switch( ib->GetElementSize() )
	{
	case 1:		width = GL_UNSIGNED_BYTE;		break;
	case 2:		width = GL_UNSIGNED_SHORT;		break;
	default:	width = GL_UNSIGNED_INT;		break;
	}

	glDrawElements( ( GLenum )primitiveType, ib->GetElementCount(), width, NULL );
}

void OpenGLDevice::DrawDummy( float angle )
{
	glTranslatef(-1.5f,0.0f,-6.0f);
	glRotatef(angle,0.0f,1.0f,0.0f);

	//glBegin(GL_QUADS);
	//	// Front Face
	//	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
	//	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
	//	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
	//	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
	//	// Back Face
	//	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
	//	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
	//	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
	//	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
	//	// Top Face
	//	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
	//	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
	//	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
	//	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
	//	// Bottom Face
	//	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Top Right Of The Texture and Quad
	//	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Top Left Of The Texture and Quad
	//	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
	//	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
	//	// Right face
	//	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
	//	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
	//	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
	//	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
	//	// Left Face
	//	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
	//	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
	//	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
	//	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
	//glEnd();
}

DEFINE_HANDLER( OpenGLDevice, WindowEvent )
{
	switch( ev.Name )
	{
	case WindowEvent::SizeChanged:
		this->OnResize();
		break;
	case WindowEvent::Closed:
		break;
	}
}

void OpenGLDevice::OnResize()
{
	Size2D size = _window->GetSize();
	int width = ( int )size.w;
	int height = ( int )size.h;
	glViewport( 0, 0, width, height );

	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();

	gluPerspective( 45.0f, (GLfloat)width/(GLfloat)height, 0.1f, 100.0f );

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
}
